#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include <errno.h>
#include <sys/mman.h>
#include <linux/videodev2.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>

#include "include/carama.h"
#include "include/lcd.h"
#include "include/ring_buffer.h"
#include "include/client.h"

#define SERVER_ADDR "192.168.137.1"
#define HOST 8080
#define BUFFER_SIZE 1024 //sokcet接受缓存区的大小

// 初始化ring_buffer
RingBuffer_t global_rb;


static int fb_fd = -1;  //lcd 描述符
pthread_t  reader1;     // LCD对应的线程
lcd_info lcd_app_info;  // 保存了LCD需要的信息


static int v4l2_fd = -1;        //摄像机 描述符

struct v4l2_format fmt = {0};  // 设置并保存摄像头格式
extern cam_buf_info buf_infos[FRAMEBUFFER_COUNT];
extern cam_fmt  cam_fmts[FRAMEBUFFER_COUNT];
pthread_t  writer1;         // 摄像机对应的线程


static int socket_fd = -1; // 客户端的描述符
pthread_t reader2;         // client对应的线程

void* carama_capture_thread(void* arg)
{
    // 分配buffer 准备视频采集
    if (v4l2_init_buffer(v4l2_fd))
        exit(EXIT_FAILURE);
    // 开始视频采集
    if (v4l2_stream_on(v4l2_fd))
        exit(EXIT_FAILURE);

    struct v4l2_buffer buf = {0};
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    for(;;) {

        for(buf.index = 0; buf.index <FRAMEBUFFER_COUNT;buf.index++)
        {
            ioctl(v4l2_fd,VIDIOC_DQBUF,&buf);

            // Convert YUYV to RGB565 and store in screen_base
            // 其实这个也是两个字节两个字节读取的
            while(ring_buffer_put(&global_rb,(char *)buf_infos[buf.index].start))
                continue;
            //printf("rb_in%d\r\n",global_rb.in &(global_rb.size - 1));
            // 数据处理完之后、再入队、往复
            // 及时出队
            ioctl(v4l2_fd, VIDIOC_QBUF, &buf);
        }

    }

}

// 客户端的接收和发送函数
// 后面的话写一个 C++的接收函数 然后看是不是需要设定发送的协议
// 一次发送多少 接收多少 接收完成后组合成图片
// 然后比LCD更卡

//实际上应该把发送和接收分开 以后接收的工作也不小

void* client_rev_send_thread(void* arg)
{
    // 维护自己私有的get_index变量
    unsigned int get_index = 0;
    char yuyv_data[global_rb.length * sizeof(char)];
    char recv_data[BUFFER_SIZE];
    memset(recv_data,0,BUFFER_SIZE);
    // 获取接收缓冲区大小
    int recv_buffer_size;
    socklen_t optlen = sizeof(recv_buffer_size);
    if (getsockopt(socket_fd, SOL_SOCKET, SO_RCVBUF, &recv_buffer_size, &optlen) == -1) {
        perror("Getsockopt for receive buffer size failed");
    }
    // 获取发送缓冲区大小
    int send_buffer_size;
    optlen = sizeof(send_buffer_size);
    if (getsockopt(socket_fd, SOL_SOCKET, SO_SNDBUF, &send_buffer_size, &optlen) == -1) {
        perror("Getsockopt for send buffer size failed");
    }

    // 打印接收缓冲区大小和发送缓冲区大小
    printf("Receive buffer size: %d bytes\n", recv_buffer_size);
    printf("Send buffer size: %d bytes\n", send_buffer_size);

    fd_set writefds;
    struct timeval timeout;
    for (;;) {
        while (ring_buffer_get(&global_rb, yuyv_data, &get_index))
            continue;
        size_t total_sent = 0;
        size_t data_length = global_rb.length;

        //非阻塞send 假设接收方一直没有调用recv 那此时我方的内核的send_buffer也就满了
        //所以缓冲区满了还可能是对面处理慢了

        // 或许直接设置为阻塞IO比较合适 这样这个线程也就直接挂起了
        // 分成两个线程 一个写入阻塞 一个读取阻塞？ -- 不好当发送缓存区满的时候就卡主了？
        //这一部分需要重新写一下
        while (total_sent < data_length) {
            // 因为是异步的 所以发送完直接返回一个小于0的数
            //?第一步send先比较待发送数据的长度len和套接字s的发送缓冲的长度， 
            //?如果len大于s的发送缓冲区的长度，该函数返回SOCKET_ERROR
            // 第二步 看看空间有没有剩余
            //  有剩余而且够了 那就直接塞进去
            // 如果不够那就等发送完了再塞入
            // 缓冲区进行发送 
            ssize_t sent_bytes = send(socket_fd, yuyv_data + total_sent, data_length - total_sent, 0);
            if (sent_bytes < 0) {
                // 本身意思一样 表示可能无法立即完成
                if (errno == EAGAIN || errno == EWOULDBLOCK) {
                    //交给select监视
                    FD_ZERO(&writefds);
                    FD_SET(socket_fd, &writefds);

                    timeout.tv_sec = 1;  // Timeout in seconds
                    timeout.tv_usec = 0;
                    //阻塞着监视了1S
                    int select_result = select(socket_fd + 1, NULL, &writefds, NULL, &timeout);
                    if (select_result < 0) {
                        perror("Select error");
                        break;
                    } else if (select_result == 0) {
                        printf("Send timeout\n");
                        //那此时岂不是有可能又重新发送了一次
                        continue;
                    } else if (FD_ISSET(socket_fd, &writefds)) {
                        continue;
                    }
                } else { //else表明send出了一点问题 可能是内存不足？之类的吧
                    printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);
                    break;
                }
            // 这个的意思就是也不知道发送了多少按这个来吧
            } else {
                total_sent += sent_bytes;
            }
        }

        ssize_t recv_bytes = recv(socket_fd, recv_data, BUFFER_SIZE, 0);
        if (recv_bytes < 0) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                FD_ZERO(&writefds);
                FD_SET(socket_fd, &writefds);

                timeout.tv_sec = 1;  // Timeout in seconds
                timeout.tv_usec = 0;
                //虽然是叫做写 但可以看到此时实际上是给到了读的fd上
                int select_result = select(socket_fd + 1, &writefds, NULL, NULL, &timeout);
                if (select_result < 0) {
                    perror("Select error");
                    break;
                } else if (select_result == 0) {
                    printf("Receive timeout\n");
                    continue;
                } else if (FD_ISSET(socket_fd, &writefds)) {
                    continue;
                }
            } else {
                printf("recv msg error: %s(errno: %d)\n", strerror(errno), errno);
                break;
            }
        } else if (recv_bytes > 0) {
            printf("recv_data: %s\n", recv_data);
        }
    }
    return NULL;


}
void* lcd_show_thread(void *arg)
{
    unsigned int get_index = 0;
    char yuyv_data[global_rb.length * sizeof(char)];
    for(;;) {
        // 说明现在满了 所以要直接跳过就行
        while(ring_buffer_get(&global_rb,yuyv_data,&get_index))
            continue;
        //printf("get_index %d\r\n",get_index&(global_rb.size-1));
        YUYV_to_RGB5652(yuyv_data, lcd_app_info,global_rb.length);

    }

}
int main(int argc,char* argv[])
{
    // 初始化LCD argv里面第2个参数
    if(argc != 3) {
        fprintf(stderr, "Usage: length %s\r\n",argv[0]);
        return -1;
    }

    //初始化lcd
    fb_fd = fb_dev_init(argv[1],&lcd_app_info);
    if(fb_fd  == -1) {
        fprintf(stderr, "open error: %s: lcd init failed %s\n",argv[1],strerror(errno));
        return -1;
    }

    // 初始化摄像头
    v4l2_fd = v4l2_dev_init(argv[2]);

    if(v4l2_fd  == -1) {
        fprintf(stderr, "open error: %s: lcd init failed %s\n",argv[2],strerror(errno));
        return -1;
    }

    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//type 类型
    fmt.fmt.pix.width = 480; //视频帧宽度 等于屏幕高度?
    fmt.fmt.pix.height = 360;//视频帧高度
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB565; //像素格式

    if (v4l2_set_format(v4l2_fd,&fmt,30))
        return -1;;


    // 更正显示信息 因为fmt会被重新设置
    lcd_app_info.min_w = fmt.fmt.pix.width > lcd_app_info.width ? lcd_app_info.width : fmt.fmt.pix.width;
    lcd_app_info.min_h = fmt.fmt.pix.height > lcd_app_info.height ? lcd_app_info.height : fmt.fmt.pix.height;

    // 初始化客户端
    socket_fd = client_init(SERVER_ADDR, HOST);
    if(socket_fd  == -1) {
        fprintf(stderr, "client init failed %s\n",strerror(errno));
        return -1;
    }

    // 假如大小是16(只支持2的n次方)个吧
    // 显示屏只支持RGB565 一个像素是2个字节
    // 表明直接完蛋了初始化失败了
    if(init_ring_buffer(&global_rb,16,lcd_app_info.min_w * lcd_app_info.min_h * 2))
        exit(EXIT_FAILURE);



    pthread_create(&writer1, NULL, carama_capture_thread, NULL);
    pthread_create(&reader1, NULL, lcd_show_thread, NULL);
    pthread_create(&reader2, NULL, client_rev_send_thread, NULL);
    
    //等待读进程完成
    pthread_join(writer1, NULL);
    // 等待读线程完成
    pthread_join(reader1, NULL);
    pthread_join(reader2, NULL);
    // 释放掉内存
    release_ring_buffer(&global_rb);
    //关闭连接
    close(socket_fd);
    return 0;

}